﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using AuxEnvironment;
using System.Drawing;

namespace GraphLib
{
    #region Класс граф
    /// <summary>
    /// Класс, описывающий граф
    /// </summary>
    public class Graph
    {
        public BindingList<GraphPoint> Points; // Список точек графа
        public BindingList<GraphLink> Links; // Список дуг графа
        /// <summary>
        /// Конструктор
        /// </summary>
        public Graph()
        {
            this.Points = new BindingList<GraphPoint>();
            this.Links = new BindingList<GraphLink>();
        }

        /// <summary>
        /// Отрисовка точек
        /// </summary>
        public void DrawPoints()
        {
            if (Points != null)
            {
                GL.PointSize(4);
                foreach (var item in Points)
                {
                    GL.Begin(BeginMode.Points);
                    GL.Color4(Color4.Red);
                    GL.Vertex2(item.X, item.Y);
                    GL.End();

                    item.TR.Label = item.Name;
                    item.TR.Render(item.X, item.Y);
                }
            }
        }

        /// <summary>
        /// Отрисовка дуг
        /// </summary>
        public void DrawLinks()
        {
            foreach (var item in Links)
            {
                item.Draw(4, 20);
                item.TR.Label = item.Weight.ToString();
                item.TR.Render((int)((item.P1.X + item.P2.X) / 2), (int)((item.P1.Y + item.P2.Y) / 2));
            }
        }

        /// <summary>
        /// Отрисовка графа
        /// </summary>
        public void Draw()
        {
            DrawLinks();
            DrawPoints();
        }

        #region Вспомогательные методы графа
        /// <summary>
        /// Возвращает точку с именем Name
        /// </summary>
        /// <param name="Name">Имя искомой точки</param>
        /// <returns>Найденная точка, либо null (если нет точки с именем Name)</returns>
        public GraphPoint GetPointByName(string Name)
        {
            return Points.FirstOrDefault(item => item.Name.ToUpper() == Name.ToUpper());
        }

        /// <summary>
        /// Найти все соседние точки указанной точки. Причем соседние точки лежат на дугах, исходящих из указанной точки
        /// </summary>
        /// <param name="point">Точка</param>
        /// <returns>Соседние точки</returns>
        public List<GraphPoint> GetNearbyPoints(GraphPoint point)
        {
            //Найти все дуги, которые принадлежат одной точке
            var listLdStraightLinks = Links.ToList().FindAll(link =>
                                                                 {
                                                                     switch (link.Ld)
                                                                     {
                                                                         case LinkDirection.LdStraight:
                                                                             return link.P1.Equals(point);
                                                                         case LinkDirection.LdBoth:
                                                                             return link.P1.Equals(point) || link.P2.Equals(point);
                                                                         default:
                                                                             return false;
                                                                     }
                                                                 });
            if (listLdStraightLinks.Count == 0) return null;
            var listNearbyPoints = new List<GraphPoint>();
            listLdStraightLinks.ForEach(link =>
                                            {
                                                switch (link.Ld)
                                                {
                                                    case LinkDirection.LdStraight:
                                                    case LinkDirection.LdBoth:
                                                        {
                                                            listNearbyPoints.Add(link.P2);
                                                            break;
                                                        }
                                                }
                                            });
            return listNearbyPoints;
        }

        /// <summary>
        /// Найти дугу по двум точкам. Возвращается первая дуга, подходящая под критерий поиска
        /// </summary>
        /// <param name="p1">Начальная точка</param>
        /// <param name="p2">Конечная точка</param>
        /// <returns>Дуга</returns>
        public GraphLink FindFirstLink(GraphPoint p1, GraphPoint p2)
        {
            return Links.FirstOrDefault(link => link.P1.Equals(p1) && link.P2.Equals(p2));
        }

        /// <summary>
        /// Найти дугу по двум точкам и направлению. Возвращается первая дуга, подходящая под критерий поиска
        /// </summary>
        /// <param name="p1">Начальная точка</param>
        /// <param name="p2">Конечная точка</param>
        /// <param name="linkDirection">Направление</param>
        /// <returns>Дуга</returns>
        public GraphLink FindFirstLink(GraphPoint p1, GraphPoint p2, LinkDirection linkDirection)
        {
            return Links.FirstOrDefault(link => link.P1.Equals(p1) && link.P2.Equals(p2) && link.Ld.Equals(linkDirection));
        }

        /// <summary>
        /// Найти дугу по одной точке. Возвращается первая дуга, подходящая под критерий поиска
        /// </summary>
        /// <param name="p1">Точка</param>
        /// <returns>Дуга</returns>
        public GraphLink FindFirstLink(GraphPoint p1)
        {
            return Links.FirstOrDefault(link => link.P1.Equals(p1) || link.P2.Equals(p1));
        }

        public void DeleteUnusedLinks(GraphPoint p1)
        {
            GraphLink graphLink = null;
            graphLink = FindFirstLink(p1);
            while (graphLink != null)
            {
                for (int i = 0; i < Links.Count; ++i)
                {
                    if (Links[i] == graphLink)
                    {
                        Links.RemoveAt(i);
                    }

                }
                graphLink = FindFirstLink(p1);
            }
        }

        #endregion
    }
    #endregion

    #region Класс точки
    /// <summary>
    /// Класс, описывающий точку графа
    /// </summary>
    public class GraphPoint
    {
        public string Name { get; set; } // Имя точки (уникальный идентификатор)
        public int X { get; set; } // X Координата
        public int Y { get; set; } // Y Координата
        public TextRenderer TR;
        
        /// <summary>
        /// Метка
        /// </summary>
        public int Mark { get; set; }
        
        /// <summary>
        /// Постоянная или временная метка
        /// </summary>
        public bool ConstantMark { get; set; }

        /// <summary>
        /// Конструктор
        /// </summary>
        public GraphPoint()
        {
            this.X = 0;
            this.Y = 0;
            this.Name = "";
            this.TR = new TextRenderer(new Font("", 12, GraphicsUnit.Point), Color4.Red, "", TextSticking.TS_TOP);
        }
        
        /// <summary>
        /// Конструктор
        /// </summary>
        /// <param name="x">X координата</param>
        /// <param name="y">Y координата</param>
        /// <param name="name">имя точки</param>
        public GraphPoint(int x, int y, string name)
        {
            this.X = x;
            this.Y = y;
            this.Name = name;
            this.TR = new TextRenderer(new Font("", 12, GraphicsUnit.Point), Color4.Red, name, TextSticking.TS_TOP);
        }

        public override string ToString()
        {
            return Name + " (" + X + ", " + Y + ")";
        }
    }
    #endregion

    #region Класс дуги
    /// <summary>
    /// Направление дуги {прямое (от P1 к P2), обратное (от P2 к P1), без направления (в обе стороны)}
    /// </summary>
    public enum LinkDirection { LdStraight, /*LdReverse, */LdBoth }
    
    /// <summary>
    /// Класс, описывающий дугу
    /// </summary>
    public class GraphLink
    {
        public GraphPoint P1 { get; set; } // Первая точка
        public GraphPoint P2 { get; set; } // Вторая точка
        public LinkDirection Ld { get; set; } // Направление дуги
        public int Weight { get; set; } // Вес дуги
        public TextRenderer TR;
        public string Name
        {
            get { return ToString(); }
        }

        /// <summary>
        /// Конструктор
        /// </summary>
        public GraphLink()
        {
            this.P1 = null;
            this.P2 = null;
            this.Ld = LinkDirection.LdBoth;
            this.Weight = 0;
            this.TR = new TextRenderer(new Font("", 10, GraphicsUnit.Point), Color4.Black, "", TextSticking.TS_TOP);
        }

        /// <summary>
        /// Конструктор
        /// </summary>
        /// <param name="p1">Первая точка</param>
        /// <param name="p2">Вторая точка</param>
        public GraphLink(GraphPoint p1, GraphPoint p2)
        {
            this.P1 = p1;
            this.P2 = p2;
            this.Ld = LinkDirection.LdBoth;
            this.Weight = 0;
            this.TR = new TextRenderer(new Font("", 10, GraphicsUnit.Point), Color4.Black, "", TextSticking.TS_TOP);
        }

        /// <summary>
        /// Конструктор
        /// </summary>
        /// <param name="p1">Первая точка</param>
        /// <param name="p2">Вторая точка</param>
        /// <param name="linkDirection">Направление</param>
        public GraphLink(GraphPoint p1, GraphPoint p2, LinkDirection linkDirection)
        {
            this.P1 = p1;
            this.P2 = p2;
            this.Ld = linkDirection;
            this.Weight = 0;
            this.TR = new TextRenderer(new Font("", 10, GraphicsUnit.Point), Color4.Black, "", TextSticking.TS_TOP);
        }

        /// <summary>
        /// Конструктор
        /// </summary>
        /// <param name="p1">Первая точка</param>
        /// <param name="p2">Вторая точка</param>
        /// <param name="linkDirection">Направление</param>
        /// <param name="weight">Вес дуги</param>
        public GraphLink(GraphPoint p1, GraphPoint p2, LinkDirection linkDirection, int weight)
        {
            this.P1 = p1;
            this.P2 = p2;
            this.Ld = linkDirection;
            this.Weight = weight;
            this.TR = new TextRenderer(new Font("", 10, GraphicsUnit.Point), Color4.Black, "", TextSticking.TS_TOP);
        }

        /// <summary>
        /// Обозначение дуги, как строки
        /// </summary>
        public override string ToString()
        {
            string tmpStr = "";
            switch (Ld)
            {
                case LinkDirection.LdStraight:
                    tmpStr = "-->";
                    break;
                //case LinkDirection.LdReverse:
                //    tmpStr = "<--";
                //    break;
                case LinkDirection.LdBoth:
                    tmpStr = "---";
                    break;
            }

            return String.Format("[{0}]{1}[{2}]", P1.Name, tmpStr, P2.Name);
        }

        /// <summary>
        /// Отрисовка дуги (со стрелкой)
        /// </summary>
        /// <param name="a">ширина стрелки</param>
        /// <param name="b">длина стрелки</param>
        public void Draw(float a, float b)
        {
            GL.LineWidth(1);
            GL.Color3(0.0f, 0.0f, 0.0f);
            GL.Begin(BeginMode.Lines);
            GL.Vertex2(P1.X, P1.Y);
            GL.Vertex2(P2.X, P2.Y);
            GL.End();

            if (Ld != LinkDirection.LdBoth)
            {
                GraphPoint pFrom = P1, pTo = P2;

                //if (Ld == LinkDirection.LdReverse)
                //{
                //    pFrom = P2;
                //    pTo = P1;
                //}

                double alpha = System.Math.Abs(System.Math.Atan((double)(pTo.X - pFrom.X) / (pTo.Y - pFrom.Y)));
                double betta = System.Math.Atan((double)a / b);
                double gamma = alpha - betta;
                double c = System.Math.Sqrt(System.Math.Pow(a, 2) + System.Math.Pow(b, 2));
                double dx1 = c * System.Math.Sin(gamma);
                double dy1 = c * System.Math.Cos(gamma);

                double alpha2 = System.Math.Abs((System.Math.PI / 2) - alpha);
                double gamma2 = alpha2 - betta;
                double dx2 = c * System.Math.Cos(gamma2);
                double dy2 = c * System.Math.Sin(gamma2);

                if (pTo.Y > pFrom.Y)
                {
                    if (pTo.X < pFrom.X)
                    {
                        dx1 = -dx1;
                        dx2 = -dx2;
                    }
                }
                else
                {
                    if (pTo.X < pFrom.X)
                    {
                        dx1 = -dx1;
                        dx2 = -dx2;

                        dy1 = -dy1;
                        dy2 = -dy2;
                    }
                    else
                    {
                        dy1 = -dy1;
                        dy2 = -dy2;
                    }
                }

                GraphPoint a1 = new GraphPoint(pTo.X - (int)dx1, pTo.Y - (int)dy1, "");
                GraphPoint a2 = new GraphPoint(pTo.X - (int)dx2, pTo.Y - (int)dy2, "");

                GL.LineWidth(1);
                GL.Color3(0.0f, 0.0f, 0.0f);
                GL.Begin(BeginMode.LineStrip);
                GL.Vertex2(a1.X, a1.Y);
                GL.Vertex2(pTo.X, pTo.Y);
                GL.Vertex2(a2.X, a2.Y);
                GL.End();
            }
        }
    }
    #endregion
}
